/**
 * Copyright (c) 广州小橘灯信息科技有限公司 2016-2017, wjun_java@163.com.
 * <p>
 * Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.gnu.org/licenses/lgpl-3.0.txt
 * http://www.xjd2020.com
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.fastcms.common.utils;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Locale;
import java.util.UUID;

/**
 * @author： wjun_java@163.com
 * @date： 2021/6/22
 * @description：
 * @modifiedBy：
 * @version: 1.0
 */
public abstract class StrUtils extends StringUtils {

	public static final String DOT = ".";

	private static final int INDEX_NOT_FOUND = -1;

	public static final String UNDERLINE = "_";

	public static final String COMMA = ",";

	public static final String EMPTY = "";

	public static final String LF = "\n";

	public static final String SLASH = "/";

	public static final String ENCODE = "UTF-8";

	private static final String[] htmlChars = new String[]{"&", "<", ">", "'", "\""};

	private static final String[] escapeChars = new String[]{"&amp;", "&lt;", "&gt;", "&#39;", "&quot;"};

	private static final SnowFlake SNOW_FLAKE = new SnowFlake(1, 1);

	public static String getSnowNo() {
		return String.valueOf(SNOW_FLAKE.genNextId());
	}

	/**
	 * <p>Create a string with encoding format as utf8.</p>
	 *
	 * @param bytes the bytes that make up the string
	 * @return created string
	 */
	public static String newStringForUtf8(byte[] bytes) {
		return new String(bytes, Charset.forName(ENCODE));
	}

	/**
	 * <p>Checks if a string is  empty (""), null and  whitespace only.</p>
	 *
	 * @param cs the string to check
	 * @return {@code true} if the string is empty and null and whitespace
	 */
	public static boolean isBlank(final CharSequence cs) {
		int strLen;
		if (cs == null || (strLen = cs.length()) == 0) {
			return true;
		}
		for (int i = 0; i < strLen; i++) {
			if (!Character.isWhitespace(cs.charAt(i))) {
				return false;
			}
		}
		return true;
	}

	/**
	 * <p>Checks if a string is not empty (""), not null and not whitespace only.</p>
	 *
	 * @param str the string to check, may be null
	 * @return {@code true} if the string is not empty and not null and not whitespace
	 */
	public static boolean isNotBlank(String str) {
		return !isBlank(str);
	}

	/**
	 * <p>Checks if a str is not empty ("") or not null.</p>
	 *
	 * @param str the str to check, may be null
	 * @return {@code true} if the str is not empty or not null
	 */
	public static boolean isNotEmpty(String str) {
		return !isEmpty(str);
	}

	/**
	 * <p>Checks if a str is empty ("") or null.</p>
	 *
	 * @param str the str to check, may be null
	 * @return {@code true} if the str is empty or null
	 */
	public static boolean isEmpty(String str) {
		return str == null || str.length() == 0;
	}

	/**
	 * <p>Returns either the passed in CharSequence, or if the CharSequence is
	 * empty or {@code null}, the value of {@code defaultStr}.</p>
	 *
	 * @param str        the CharSequence to check, may be null
	 * @param defaultStr the default CharSequence to return if the input is empty ("") or {@code null}, may be null
	 * @return the passed in CharSequence, or the default
	 */
	public static String defaultIfEmpty(String str, String defaultStr) {
		return isEmpty(str) ? defaultStr : str;
	}

	/**
	 * <p>Compares two CharSequences, returning {@code true} if they represent
	 * equal sequences of characters.</p>
	 *
	 * @param str1 the first string, may be {@code null}
	 * @param str2 the second string, may be {@code null}
	 * @return {@code true} if the string are equal (case-sensitive), or both {@code null}
	 * @see Object#equals(Object)
	 */
	public static boolean equals(String str1, String str2) {
		return str1 == null ? str2 == null : str1.equals(str2);
	}

	/**
	 * <p>Removes control characters (char &lt;= 32) from both
	 * ends of this String, handling {@code null} by returning {@code null}.</p>
	 *
	 * @param str the String to be trimmed, may be null
	 * @return the trimmed string, {@code null} if null String input
	 */
	public static String trim(final String str) {
		return str == null ? null : str.trim();
	}

	/**
	 * Substring between two index.
	 *
	 * @param str   string
	 * @param open  start index to sub
	 * @param close end index to sub
	 * @return substring
	 */
	public static String substringBetween(String str, String open, String close) {
		if (str == null || open == null || close == null) {
			return null;
		}
		int start = str.indexOf(open);
		if (start != INDEX_NOT_FOUND) {
			int end = str.indexOf(close, start + open.length());
			if (end != INDEX_NOT_FOUND) {
				return str.substring(start + open.length(), end);
			}
		}
		return null;
	}

	/**
	 * <p>Joins the elements of the provided array into a single String
	 * containing the provided list of elements.</p>
	 *
	 * @param collection the Collection of values to join together, may be null
	 * @param separator  the separator string to use
	 * @return the joined String, {@code null} if null array input
	 */
	public static String join(Collection collection, String separator) {
		if (collection == null) {
			return null;
		}

		StringBuilder stringBuilder = new StringBuilder();
		Object[] objects = collection.toArray();

		for (int i = 0; i < collection.size(); i++) {
			if (objects[i] != null) {
				stringBuilder.append(objects[i]);
				if (i != collection.size() - 1 && separator != null) {
					if (separator != null) {
						stringBuilder.append(separator);
					}
				}
			}
		}

		return stringBuilder.toString();
	}

	public static String escapeJavaScript(String str) {
		return escapeJavaStyleString(str, true, true);
	}

	private static String escapeJavaStyleString(String str, boolean escapeSingleQuotes, boolean escapeForwardSLASH) {
		if (str == null) {
			return null;
		}
		try {
			StringWriter writer = new StringWriter(str.length() * 2);
			escapeJavaStyleString(writer, str, escapeSingleQuotes, escapeForwardSLASH);
			return writer.toString();
		} catch (IOException ioe) {
			// this should never ever happen while writing to a StringWriter
			return null;
		}
	}

	private static void escapeJavaStyleString(Writer out, String str, boolean escapeSingleQuote,
											  boolean escapeForwardSLASH) throws IOException {
		if (out == null) {
			throw new IllegalArgumentException("The Writer must not be null");
		}
		if (str == null) {
			return;
		}
		int sz;
		sz = str.length();
		for (int i = 0; i < sz; i++) {
			char ch = str.charAt(i);

			// handle unicode
			if (ch > 0xfff) {
				out.write("\\u" + hex(ch));
			} else if (ch > 0xff) {
				out.write("\\u0" + hex(ch));
			} else if (ch > 0x7f) {
				out.write("\\u00" + hex(ch));
			} else if (ch < 32) {
				switch (ch) {
					case '\b':
						out.write('\\');
						out.write('b');
						break;
					case '\n':
						out.write('\\');
						out.write('n');
						break;
					case '\t':
						out.write('\\');
						out.write('t');
						break;
					case '\f':
						out.write('\\');
						out.write('f');
						break;
					case '\r':
						out.write('\\');
						out.write('r');
						break;
					default:
						if (ch > 0xf) {
							out.write("\\u00" + hex(ch));
						} else {
							out.write("\\u000" + hex(ch));
						}
						break;
				}
			} else {
				switch (ch) {
					case '\'':
						if (escapeSingleQuote) {
							out.write('\\');
						}
						out.write('\'');
						break;
					case '"':
						out.write('\\');
						out.write('"');
						break;
					case '\\':
						out.write('\\');
						out.write('\\');
						break;
					case '/':
						if (escapeForwardSLASH) {
							out.write('\\');
						}
						out.write('/');
						break;
					default:
						out.write(ch);
						break;
				}
			}
		}
	}

	private static String hex(char ch) {
		return Integer.toHexString(ch).toUpperCase(Locale.ENGLISH);
	}

	/**
	 * Checks if CharSequence contains a search CharSequence irrespective of case, handling {@code null}.
	 * Case-insensitivity is defined as by {@link String#equalsIgnoreCase(String)}.
	 *
	 * <p>A {@code null} CharSequence will return {@code false}.</p>
	 *
	 * @param str       the CharSequence to check, may be null
	 * @param searchStr the CharSequence to find, may be null
	 * @return true if the CharSequence contains the search CharSequence irrespective of case or false if not or {@code
	 * null} string input
	 */
	public static boolean containsIgnoreCase(final CharSequence str, final CharSequence searchStr) {
		if (str == null || searchStr == null) {
			return false;
		}
		String str1 = str.toString().toLowerCase();
		String str2 = searchStr.toString().toLowerCase();
		return str1.contains(str2);
	}

	/**
	 * Checks if CharSequence contains a search CharSequence.
	 *
	 * @param str       the CharSequence to check, may be null
	 * @param searchStr the CharSequence to find, may be null
	 * @return true if the CharSequence contains the search CharSequence
	 */
	public static boolean contains(final CharSequence str, final CharSequence searchStr) {
		if (str == null || searchStr == null) {
			return false;
		}
		return str.toString().contains(searchStr);
	}

	/**
	 * <p>Checks if none of the CharSequences are blank ("") or null and whitespace only..</p>
	 *
	 * @param css the CharSequences to check, may be null or empty
	 * @return {@code true} if none of the CharSequences are blank or null or whitespace only
	 */
	public static boolean isNoneBlank(final CharSequence... css) {
		return !isAnyBlank(css);
	}

	/**
	 * <p>Checks if any one of the CharSequences are blank ("") or null and not whitespace only..</p>
	 *
	 * @param css the CharSequences to check, may be null or empty
	 * @return {@code true} if any of the CharSequences are blank or null or whitespace only
	 */
	public static boolean isAnyBlank(final CharSequence... css) {
		if (ArrayUtils.isEmpty(css)) {
			return true;
		}
		for (final CharSequence cs : css) {
			if (isBlank(cs)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * <p>Check if a CharSequence starts with a specified prefix.</p>
	 *
	 * <p>{@code null}s are handled without exceptions. Two {@code null}
	 * references are considered to be equal. The comparison is case sensitive.</p>
	 *
	 * @param str    the CharSequence to check, may be null
	 * @param prefix the prefix to find, may be null
	 * @return {@code true} if the CharSequence starts with the prefix, case sensitive, or both {@code null}
	 * @see java.lang.String#startsWith(String)
	 */
	public static boolean startsWith(final CharSequence str, final CharSequence prefix) {
		return startsWith(str, prefix, false);
	}

	/**
	 * <p>Check if a CharSequence starts with a specified prefix (optionally case insensitive).</p>
	 *
	 * @param str        the CharSequence to check, may be null
	 * @param prefix     the prefix to find, may be null
	 * @param ignoreCase indicates whether the compare should ignore case (case insensitive) or not.
	 * @return {@code true} if the CharSequence starts with the prefix or both {@code null}
	 * @see java.lang.String#startsWith(String)
	 */
	private static boolean startsWith(final CharSequence str, final CharSequence prefix, final boolean ignoreCase) {
		if (str == null || prefix == null) {
			return str == null && prefix == null;
		}
		if (prefix.length() > str.length()) {
			return false;
		}
		if (ignoreCase) {
			String lowerCaseStr = str.toString().toLowerCase();
			String lowerCasePrefix = str.toString().toLowerCase();
			return lowerCaseStr.startsWith(lowerCasePrefix);
		} else {
			return str.toString().startsWith(prefix.toString());
		}
	}

	/**
	 * <p>Case insensitive check if a CharSequence starts with a specified prefix.</p>
	 *
	 * <p>{@code null}s are handled without exceptions. Two {@code null}
	 * references are considered to be equal. The comparison is case insensitive.</p>
	 *
	 * @param str    the CharSequence to check, may be null
	 * @param prefix the prefix to find, may be null
	 * @return {@code true} if the CharSequence starts with the prefix, case insensitive, or both {@code null}
	 * @see java.lang.String#startsWith(String)
	 */
	public static boolean startsWithIgnoreCase(final CharSequence str, final CharSequence prefix) {
		return startsWith(str, prefix, true);
	}

	/**
	 * <p>Deletes all whitespaces from a String as defined by
	 * {@link Character#isWhitespace(char)}.</p>
	 *
	 * @param str the String to delete whitespace from, may be null
	 * @return the String without whitespaces, <code>null</code> if null String input
	 */
	public static String deleteWhitespace(String str) {
		if (isEmpty(str)) {
			return str;
		}
		int sz = str.length();
		char[] chs = new char[sz];
		int count = 0;
		for (int i = 0; i < sz; i++) {
			if (!Character.isWhitespace(str.charAt(i))) {
				chs[count++] = str.charAt(i);
			}
		}
		if (count == sz) {
			return str;
		}
		return new String(chs, 0, count);
	}

	/**
	 * <p>Compares two CharSequences, returning {@code true} if they represent
	 * equal sequences of characters, ignoring case.</p>
	 *
	 * @param str1 the first string, may be null
	 * @param str2 the second string, may be null
	 * @return {@code true} if the string are equal, case insensitive, or both {@code null}
	 */
	public static boolean equalsIgnoreCase(String str1, String str2) {
		return str1 == null ? str2 == null : str1.equalsIgnoreCase(str2);
	}

	/**
	 * Splits the provided text into an array with a maximum length,
	 * separators specified.
	 * If separatorChars is empty, divide by blank.
	 * @param str  the String to parse, may be null
	 * @return an array of parsed Strings
	 */
	@SuppressWarnings("checkstyle:WhitespaceAround")
	public static String[] split(final String str, String separatorChars) {
		if (str == null) {
			return null;
		}
		if (str.length() == 0) {
			return new String[0];
		}
		if (separatorChars == null){
			separatorChars = " +";
		}
		return str.split(separatorChars);
	}

	public synchronized static String uuid() {
		return UUID.randomUUID().toString().replace("-", "");
	}


	public static String escapeHtml(String content) {
		return isBlank(content) ? content : StringUtils.replaceEach(unEscapeHtml(content), htmlChars, escapeChars);
	}

	public static String unEscapeHtml(String content) {
		return isBlank(content) ? content : StringUtils.replaceEach(content, escapeChars, htmlChars);
	}

}
